﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Jy.Abp.GeneralTree;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using MongoDB.Driver;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MongoDB.DependencyInjection;
using Volo.Abp.Reflection;

namespace Jy.Abp.GeneralTree.MongoDB;

public static class IAbpDbContextRegistrationOptionsBuilderExtensions
{
    public static void AddGeneralTreeRepository<TEntity, TKey>(this IAbpMongoDbContextRegistrationOptionsBuilder builder)
    {
        builder.AddGeneralTreeRepository(typeof(TEntity), typeof(TKey));
    }

    public static void AddGeneralTreeRepository(
        this IAbpMongoDbContextRegistrationOptionsBuilder builder,
        Type entityType,
        Type primaryKeyType)
    {
        var options = builder as AbpMongoDbContextRegistrationOptions;
        var serviceType = typeof(IGeneralTreeRepository<,>)
            .MakeGenericType(entityType, primaryKeyType);

        var impType = typeof(MongoGeneralTreeRepository<,,>)
            .MakeGenericType(options.DefaultRepositoryDbContextType,
                             entityType,
                             primaryKeyType);

        //options.Services.AddTransient(serviceType, impType);
        RegisterService(options.Services, serviceType, impType, true);
    }

    public static void AddGeneralTreeRepositories(this IAbpMongoDbContextRegistrationOptionsBuilder builder)
    {
        var options = builder as AbpMongoDbContextRegistrationOptions;
        foreach (var entityType in GetEntityTypes(options.DefaultRepositoryDbContextType))
        {
            var primaryKeyType = EntityHelper.FindPrimaryKeyType(entityType);
            builder.AddGeneralTreeRepository(entityType, primaryKeyType);
        }
    }

    private static IEnumerable<Type> GetEntityTypes(Type dbContextType)
    {
        return
            from property in dbContextType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where
                ReflectionHelper.IsAssignableToGenericType(property.PropertyType, typeof(IMongoCollection<>)) &&
                typeof(IGeneralTree).IsAssignableFrom(property.PropertyType.GenericTypeArguments[0])
            select property.PropertyType.GenericTypeArguments[0];
    }

    private static void RegisterService(
       IServiceCollection services,
       Type serviceType,
       Type implementationType,
       bool replaceExisting,
       bool isReadOnlyRepository = false)
    {
        var descriptor = ServiceDescriptor.Transient(serviceType, implementationType);

        if (isReadOnlyRepository)
        {
            services.OnActivated(descriptor, context =>
            {
                var repository = context.Instance.As<IRepository>();
                ObjectHelper.TrySetProperty(repository.As<IRepository>(), x => x.IsChangeTrackingEnabled, _ => false);
            });
        }

        if (replaceExisting)
        {
            services.Replace(descriptor);
        }
        else
        {
            services.TryAdd(descriptor);
        }
    }
}
